/**
 *   This file is part of ppi-server.
 *
 *   ppi-server is free software: you can redistribute it and/or modify
 *   it under the terms of the Lesser GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   ppi-server is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   Lesser GNU General Public License for more details.
 *
 *   You should have received a copy of the Lesser GNU General Public License
 *   along with ppi-server.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef THREAD_H_
#define THREAD_H_

#include <pthread.h>
#include <errno.h>
#include <unistd.h>

#include <string>
#include <vector>
#include <map>

#include "debug.h"

#include "../pattern/util/IStatusLogPattern.h"

using namespace design_pattern_world;
using namespace std;

struct mutexnames_t
{
	/**
	 * defined name for mutex with getMutex()
	 */
	string name;
	/**
	 * thread id which have locked mutex.<br />
	 * this id is only defined if MUTEXLOCKDEBUG is defined
	 */
	pid_t threadid;
};

/**
 * globaly mutex for variables g_mMutex and g_mCondition
 */
extern pthread_mutex_t g_READMUTEX;
/**
 * all defined mutex with names and thread ids for debugging
 */
extern map<pthread_mutex_t*, mutexnames_t> g_mMutex;
/**
 * all defined conditions with names
 */
extern map<pthread_cond_t*, string> g_mCondition;

/**
 * base class for all threads.<br />
 * to use compiling with pthread's
 * application need library pthread with option -lpthread
 * by creating with make
 *
 * @autor Alexander Kolli
 * @version 1.0.0
 */
class Thread : public virtual IStatusLogPattern
{
	public:
		/**
		 * creating instance of thread
		 *
		 * @param threadName Name of thread to identify in logmessages
		 * @param waitInit if flag is true (default), starting thread waiting until this thread initial with method init()
		 */
		//Thread(string threadName, bool waitInit= true);
		/**
		 * creating instance of thread
		 *
		 * @param threadName Name of thread to identify in logmessages
		 * @param defaultSleep sleeping for default time in microseconds
		 * @param waitInit if flag is true (default), starting thread waiting until this thread initial with method init()
		 */
		Thread(string threadName, useconds_t defaultSleep, bool waitInit= true);
		/**
		 * start method to running the thread paralell
		 *
		 * @param args arbitary optional defined parameter to get in initialisation method init
		 * @param bHold should the caller wait of thread by ending.<br />
		 * 				default is false
		 * @return NULL when all ok if bHold is false, otherwise the returnvalue of the thread in an void pointer
		 */
		virtual void* start(void *args= NULL, bool bHold= false);
		/**
		 * set last position for status information
		 *
		 * @param file in which file the poisiton be set
		 * @param line on which line in the file the position set
		 * @param identif any named position identification
		 */
		static void position(const string file, const int line, const string identif)
		{ positionA(file, line, identif, NULL, NULL); };
		/**
		 * set last position for status information
		 *
		 * @param file in which file the poisiton be set
		 * @param line on which line in the file the position set
		 * @param identif any named position identification
		 * @param info2 varios information as an string
		 */
		static void position(const string file, const int line, const string identif, const string info2)
		{ positionA(file, line, identif, &info2, NULL); };
		/**
		 * set last position for status information
		 *
		 * @param file in which file the poisiton be set
		 * @param line on which line in the file the position set
		 * @param identif any named position identification
		 * @param ninfo2 variaos information as an integer
		 */
		static void position(const string file, const int line, const string identif, const int ninfo2)
		{ positionA(file, line, identif, NULL, &ninfo2); };
		/**
		 * set last position for status information
		 *
		 * @param file in which file the poisiton be set
		 * @param line on which line in the file the position set
		 * @param identif any named position identification
		 * @param info2 varios information as an string
		 * @param ninfo2 variaos information as an integer
		 */
		static void position(const string file, const int line, const string identif, const string info2, const int ninfo2)
		{ positionA(file, line, identif, &info2, &ninfo2); };
		/**
		 * set last position for status information
		 *
		 * @param file in which file the poisiton be set
		 * @param line on which line in the file the position set
		 * @param identif any named position identification
		 * @param info2 varios information as an string
		 * @param ninfo2 variaos information as an integer
		 */
		static void positionA(const string file, const int line, const string identif, const string* info2, const int* ninfo2);
		/**
		 * first initialization from running thread
		 *
		 * @param threadName name of the thread
		 * @param thread object of an thread which contains the IStatusLogPattern
		 */
		void initstatus(const string threadName, IStatusLogPattern* thread);
		/**
		 * set status for information
		 *
		 * @param thread object of an thread which contains the IStatusLogPattern
		 */
		void statusattrib(IStatusLogPattern* thread)
		{ statusattrib(thread, NULL, NULL); };
		/**
		 * set status for information
		 *
		 * @param ninfo1 integer information
		 */
		void statusattrib(int ninfo1)
		{ statusattrib(NULL, NULL, &ninfo1); };
		/**
		 * set status for information
		 *
		 * @param info1 string information
		 */
		void statusattrib(string info1)
		{ statusattrib(NULL, &info1, NULL); };
		/**
		 * set status for information
		 *
		 * @param info1 string information
		 * @param ninfo1 integer information
		 */
		void statusattrib(string info1, int ninfo1)
		{ statusattrib(NULL, &info1, &ninfo1); };
		/**
		 * set status for information
		 *
		 * @param thread object of an thread which contains the IStatusLogPattern
		 * @param info1 string information
		 * @param ninfo1 integer information
		 */
		void statusattrib(IStatusLogPattern* thread, string* info1, int* ninfo1);
		/**
		 * get status information for last reached position with time
		 * for all running threads.<br />
		 * <br />
		 * On starting static method with no params, method returning only an number of running threads<br />
		 * elsewhere by any params it calls for all threads the defined virtual getStatusInfo(params, pos, ...)<br />
		 * if in the params be set 'threads:<system thread number pid_t>' this method calling only the virtual
		 * getStatusInfo for the given thread.
		 *
		 * @param params to show which status info
		 * @return status information
		 */
		static string getStatusInfo(string params);
		/**
		 * remove thread position from status list
		 *
		 * @param threadid id of thread which should be removed
		 */
		void removestatus(const pid_t threadid);
		/**
		 * to ask whether the thread should stopping.<br />
		 * This method should be call into running thread to know whether the thread should stop.
		 *
		 * @return true if the thread should stop
		 */
		bool stopping();
		/**
		 * external query whether the thread is running
		 *
		 * @return true if thread is running
		 */
		bool running();
		/**
		 *  external command to stop thread
		 *
		 * @param bWait calling rutine should wait until the thread is stopping
		 */
		virtual void *stop(const bool bWait)
		{ return stop(&bWait); };
		/**
		 *  external command to stop thread
		 *
		 * @param bWait calling rutine should wait until the thread is stopping
		 */
		virtual void *stop(const bool *bWait= NULL);
		/**
		 * the thread will be uncoupled from the starting thread
		 *
		 * @return error level if exist, otherwise 0
		 */
		int detach();
		/**
		 * return id from thread
		 *
		 * @return id
		 */
		static pid_t gettid();
		/**
		 * creating an new mutex to lock the thread or an hole part
		 *
		 * @param name name of mutex for logging information
		 * @return mutex variable
		 */
   		static pthread_mutex_t* getMutex(string name);
   		/**
   		 * creating an new condition to wait for an other thread
   		 *
		 * @param name name of condition for logging information
		 * @return mutex variable
		 */
   		static pthread_cond_t* getCondition(string name);
   		/**
   		 * locking thread
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param mutex mutex variable which should be locked
   		 */
   		static int mutex_lock(string file, int line, pthread_mutex_t *mutex);
   		/**
   		 * try to lock an thread.<br />
   		 * If THE LOCK IS successful, returnvalue is 0, by locking from an other thread,
   		 * returnvalue is EBUSY, else returnvalue is an ERROR seen in variable erno
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param mutex mutex variable which should be unlocked
   		 * @return 0 if successful, all other is an ERROR or EBUSY
   		 */
   		static int mutex_trylock(string file, int line, pthread_mutex_t *mutex);
   		/**
   		 * unlock thread
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param mutex mutex variable which should be unlocked
   		 * @return 0 if successful, all other is an ERROR
   		 */
   		static int mutex_unlock(string file, int line, pthread_mutex_t *mutex);
   		/**
   		 * method should wait to get condition
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param cond pointer to condition
   		 * @param mutex pointer to mutex in witch he should wait
   		 * @param time how much time the method maximal should wait if set
   		 * @param absolute if time be set, this parameter define whether the time is relative or absolute (default:true)
   		 * @return 0 if successful, ETIMEDOUT if end of time reached, otherwise an error occured
   		 */
   		static int conditionWait(string file, int line, pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec *time= NULL, const bool absolute= true);
   		/**
   		 * arose one or more threads which wating for given condition
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param cond pointer to condition
   		 * @return 0 if successful, otherwise an error occured
   		 */
   		static int arouseCondition(string file, int line, pthread_cond_t *cond);
   		/**
   		 * arose all threads which waiting for the given condition
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param cond pointer to condition
   		 * @return 0 if successful, otherwise an error occured
   		 */
   		static int arouseAllCondition(string file, int line, pthread_cond_t *cond);
   		/**
   		 * destroy mutex in the map vector and give it free to hold it petit for performance
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param mutex mutex variable which should deleted
   		 */
   		static void destroyMutex(string file, int line, pthread_mutex_t *mutex);
   		static void destroyAllMutex();
   		/**
   		 * destroy condition in map vector and give it free
   		 *
   		 * @param file in which file this method be called
   		 * @param line on which line in the file this method be called
   		 * @param cpmd pointer of condition which should deleted
   		 */
   		static void destroyCondition(string file, int line, pthread_cond_t *cond);
   		static void destroyAllConditions();
   		/**
   		 * return name of mutex
   		 *
   		 * @param mutex pointer of mutex
   		 * @return name of mutex
   		 */
   		static string getMutexName(pthread_mutex_t *mutex);
   		/**
   		 * return name of condition
   		 *
   		 * @param cond pointer of condition
   		 * @return name of condition
   		 */
   		static string getConditionName(pthread_cond_t *cond);
   		/**
   		 * method returning name of thread
   		 *
   		 * @return name of thread
   		 */
		string getThreadName();
		/**
		 * returning thread id from OS
		 *
		 * @return thread id
		 */
		pid_t getThreadID()
		{ return m_nThreadId; };
		/**
		 * destructor of class Thread
		 */
		virtual ~Thread();

	protected:
		/**
		 * abstract method to initial the thread
		 * in the extended class.<br />
		 * this method will be called before running
		 * the method execute
		 *
		 * @param args user defined parameter value or array,<br />
		 * 				comming as void pointer from the external call
		 * 				method start(void *args).
		 * @return boolean whether the method execute can start
		 */
		virtual bool init(void *args)=0;
		/**
		 * abstract method to running thread
		 * in the extended class.<br />
		 * This method starting again when ending without an sleeptime
		 * if the method stop() isn't call.
		 */
		virtual void execute()=0;
		/**
		 * protected initialization for given info points
		 * to write into the status information
		 *
		 * @param params parameter set by call getStatusInfo from main method
		 * @param pos position struct see pos_t
		 * @param elapsed seconds be elapsed since last position time pos_t.time
		 * @param time from last position pos_t.time converted in an string
		 */
		virtual string getStatusInfo(string params, pos_t& pos, time_t elapsed, string lasttime);
		/**
		 * sleep an default microseconds time.<br />
		 * Some older computer needs to much cpu time if an thread running
		 * all the time. This waiting time can be set in the constructor of this class.<br />
		 * The extended classes can so define an sleeping time on the right position
		 * with this protected method.
		 */
		void sleepDefaultTime()
		{ usleep(m_nDefaultSleep); };
		/**
		 * abstract method to ending the thread.<br />
		 * This method will be called if any other or own thread
		 * calling method stop().
		 */
		virtual void ending()=0;
		/**
		 * this method defining the name whitch be showen in the log-files.
		 *
		 * @param threadName name which should be showen
		 */
		void setThreadLogName(string threadName);




	private:
		/**
		 * thrad id from system (OS/Linux)
		 */
		pid_t m_nThreadId;
		/**
		 * thread ID from current thread
		 */
		pthread_t m_nPosixThreadID;
		/**
		 * should thread waiting for initaliazen of thread
		 */
		bool m_bWaitInit;
		/**
		 * user defined param(s) by beginning thread with ->start()
		 */
		void *m_pArgs;
		/**
		 * flag to should stopping thread
		 */
		bool m_bStop;
		/**
		 * flag set while running thread
		 */
		bool m_bRun;
		/**
		 * if flag is set, starting thread
		 * waiting while this thread running
		 */
		bool m_bHold;
		/**
		 * default sleep time in microseconds.<br />
		 * Some older computer needs to much cpu time if an thread running
		 * all the time. This time can be set in the constructor of this class.<br />
		 * The extended classes can so define an sleeping time on the right position
		 * with the protected method sleepDefaultTime().
		 */
		useconds_t m_nDefaultSleep;
		/**
		 * specificed name of thread in constructor
		 */
		string m_sThreadName;
		/**
		 * whether thread is initialiced for stopping
		 */
		pthread_mutex_t* m_STOPTHREAD;
		/**
		 * whether thread is running
		 */
		pthread_mutex_t* m_RUNTHREAD;
		/**
		 * lock to get threadname
		 */
		pthread_mutex_t* m_THREADNAME;
		/**
		 * mutex lock for start or stop thread
		 */
		pthread_mutex_t* m_STARTSTOPTHREAD;
		/**
		 * condition for start or stop thread
		 */
		pthread_cond_t* m_STARTSTOPTHREADCOND;
		/**
		 * map of position information for all threads
		 */
		static map<pid_t, pos_t> m_mStatus;
		/**
		 * mutex lock for set or reading status
		 */
		static pthread_mutex_t* m_POSITIONSTATUS;

		void run();
		static void * EntryPoint(void*);
		/**
		 * private static initialization for given info points
		 * to write into the status information
		 *
		 * @param params parameter set by call getStatusInfo from main method
		 * @param pos position struct see pos_t
		 * @param elapsed seconds be elapsed since last position time pos_t.time
		 * @param time from last position pos_t.time converted in an string
		 */
		static string getStatus(string params, pos_t& pos, time_t elapsed, string lasttime);

};

#define LOCK(mutex) Thread::mutex_lock(__FILE__, __LINE__, mutex)
#define TRYLOCK(mutex) Thread::mutex_trylock(__FILE__, __LINE__, mutex)
#define UNLOCK(mutex) Thread::mutex_unlock(__FILE__, __LINE__, mutex)
#define CONDITION(cond, mutex) Thread::conditionWait(__FILE__, __LINE__, cond, mutex)
#define TIMECONDITION(cond, mutex, time) Thread::conditionWait(__FILE__, __LINE__, cond, mutex, time)
#define RELTIMECONDITION(cond, mutex, time) Thread::conditionWait(__FILE__, __LINE__, cond, mutex, time, false)
#define AROUSE(cond) Thread::arouseCondition(__FILE__, __LINE__, cond)
#define AROUSEALL(cond) Thread::arouseAllCondition(__FILE__, __LINE__, cond)
#define DESTROYMUTEX(mutex) Thread::destroyMutex(__FILE__, __LINE__, mutex)
#define DESTROYCOND(cond) Thread::destroyCondition(__FILE__, __LINE__, cond)
#define POS(identif) Thread::position(__FILE__, __LINE__, identif)
#define POSS(identif, info2) Thread::position(__FILE__, __LINE__, identif, info2)
#define POSN(identif, ninfo2) Thread::position(__FILE__, __LINE__, identif, ninfo2)
#define POSSN(identif, info2, ninfo2) Thread::position(__FILE__, __LINE__, identif, info2, ninfo2)

#endif /*THREAD_H_*/
